package Tasks;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;

import Plants.PowerLine;
import Plants.PowerPlant;
import SmartGrid.ExecutionEngine;
import SmartGrid.SmartGrid;
import Tools.Log;

/**
 * The core management algorithm to maintain the smart grid
 * @author jeffrey
 *
 */
public class ManageSmartGrid extends MyRunnable{
	
	private SmartGrid smartgrid;
	
	public ManageSmartGrid(SmartGrid smartgrid)
	{
		this.smartgrid = smartgrid;
	}

	@Override
	public synchronized void run() {
		if(super.haltExecution()) return;
		//managementAlgorithm1();
		//managementAlgorithm2();
		managementAlgorithm3();
	}
	
	/**
	 * Basic management algorithm.  All plants operate independently.  
	 * If a plant requires more energy, it raises its own electric output.
	 * Most wasteful.
	 */
	private void managementAlgorithm1()
	{
		Iterator<PowerPlant> allPlants = smartgrid.allPlants.values().iterator();
		while(allPlants.hasNext())
		{
			PowerPlant plant = allPlants.next();
			float excessEnergy = plant.getAvailableEnergy() - plant.getResponsibilities(); 
			if(excessEnergy < 0)
			{
				plant.raiseElectricOutput(plant.getElectricOutput() + 100);
			}
		}
	}
	
	/**
	 * If a plant requires more energy, it will find the closest neighbor who can supply
	 * the need.  If no neighbor exists, the plant will raise its own electric output.
	 */
	private void managementAlgorithm2()
	{
		Iterator<PowerPlant> needyPlants = getNeedyPowerPlants().iterator();
		while(needyPlants.hasNext())
		{
			PowerPlant needyPlant = needyPlants.next();
			int need = -needyPlant.getExcessEnergy(); 
			PowerPlant neighbor = needyPlant.getNearestNeighborWithEnergy(need);
			if(neighbor != null)
			{
				SendEnergy transfer = new SendEnergy(neighbor, needyPlant, need);
				Log.Write(transfer.toString());
				ExecutionEngine.addTask(transfer);
			}
			else
			{
				needyPlant.raiseElectricOutput(needyPlant.getElectricOutput() + 100);
			}
		}
	}
	
	/**
	 * For each needy power plant, does a breadth first search to find power plants with excess energy.
	 * All power plants found in the search will route energy to the needy power plant until its need is satisfied
	 */
	private void managementAlgorithm3()
	{
		Iterator<PowerPlant> needyPlants = getNeedyPowerPlants().iterator();
		while(needyPlants.hasNext())
		{
			PowerPlant needy = needyPlants.next();
			managementAlgorithm3recurse(needy, needy, new ArrayBlockingQueue<PowerPlant>(100));
			if(needy.getExcessEnergy() < 0)
				needy.raiseElectricOutput(needy.getElectricOutput() + 100);
			resetVisited();
		}
	}
	
	private void managementAlgorithm3recurse(PowerPlant source, PowerPlant start, Queue<PowerPlant> queue)
	{
		if(source.getExcessEnergy() >= 0 || start.isVisited())
			return;
		start.setVisited(true);
		Iterator<PowerLine> lines = start.getPowerLines().values().iterator();
		while(lines.hasNext())
		{
			PowerPlant neighbor = lines.next().getEndNode(start);
			if(neighbor.isVisited()) continue;
			if(neighbor.getExcessEnergy() > 0 && source.getExcessEnergy() < 0)
			{
				int excess = neighbor.getExcessEnergy();
				neighbor.incrementAvailableEnergy(-excess, true);
				source.incrementAvailableEnergy(excess, true);
				Log.Write("Transfer " + excess + " energy from " + neighbor.getName() + " to " + source.getName());
				System.out.println("Transfer " + excess + " energy from " + neighbor.getName() + " to " + source.getName());
			}
			queue.add(neighbor);
		}
		while(!queue.isEmpty())
		{
			PowerPlant neighbor = queue.poll();
			managementAlgorithm3recurse(source, neighbor, queue);
		}
		
		
		/*lines = start.getPowerLines().values().iterator();
		while(lines.hasNext())
		{
			PowerPlant neighbor = lines.next().getEndNode(start);
			managementAlgorithm3recurse(source, neighbor);
		}*/
	}
	
	/**
	 * Get power plants with an excess energy amount of at least energy
	 * @param energy
	 * @return
	 */
	private Set<PowerPlant> getPlantsWithEnergy(int energy)
	{
		Set<PowerPlant> availablePlants = new HashSet<PowerPlant>();
		Set<PowerPlant> plants = smartgrid.getAllPlants();
		Iterator<PowerPlant> iter = plants.iterator();
		while(iter.hasNext())
		{
			PowerPlant p = iter.next();
			if(p.getAvailableEnergy() - p.getResponsibilities() >= energy)
			{
				availablePlants.add(p);
			}
		}
		return availablePlants;
	}
	
	/**
	 * Get all power plants that need extra energy
	 * @return
	 */
	private Set<PowerPlant> getNeedyPowerPlants()
	{
		Iterator<PowerPlant> allPlants = smartgrid.allPlants.values().iterator();
		Set<PowerPlant> needy = new HashSet<PowerPlant>();
		while(allPlants.hasNext())
		{
			PowerPlant plant = allPlants.next();
			float excessEnergy = plant.getAvailableEnergy() - plant.getResponsibilities(); 
			if(excessEnergy < 0)
				needy.add(plant);
		}
		return needy;
	}
	
	private void resetVisited()
	{
		Iterator<PowerPlant> allPlants = smartgrid.allPlants.values().iterator();
		while(allPlants.hasNext())
		{
			PowerPlant p = allPlants.next();
			p.setVisited(false);
		}
	}

}
